package co.edu.udea.iw.prestamos.modelo.dao.hibernate;

import java.io.Serializable;
import java.util.List;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import co.edu.udea.iw.prestamos.modelo.dao.CrudDAO;
import co.edu.udea.iw.prestamos.util.exception.DatabaseException;
import co.edu.udea.iw.prestamos.util.exception.RegistryNotFoundException;

/**
 * DAO genérico con implementación de operaciones CRUD en Hibernate
 * @author Santiago Álvarez Rodríguez <santiaro90@gmail.com>
 * @since 07-07-2014
 * @version 1.0
 *
 * @param <T> POJO que maneja el DAO
 * @param <ID> Tipo de la PK de la entidad
 */
public abstract class CrudDAOHibernate<T, ID extends Serializable>
	implements CrudDAO<T, ID> {

	@Autowired
	private SessionFactory sessionFactory;
	
	/**
	 * Clase del POJO que maneja el DAO
	 */
	private final Class<T> type;
	
	public CrudDAOHibernate(Class<T> type) {
		this.type = type;
	}
	
	/**
	 * @see CrudDAO#crear(Object)
	 */
	@Override
	@Transactional(propagation = Propagation.MANDATORY)
	public T crear(T objeto) throws DatabaseException {
		try {
			getCurrentSession().save(objeto);
			getCurrentSession().flush();
			
			return objeto;
		} catch (HibernateException e) {
			throw new DatabaseException(e);
		}
	}

	/**
	 * @see CrudDAO#eliminar(Object)
	 */
	@Override
	@Transactional(propagation = Propagation.MANDATORY)
	public T eliminar(T objeto) throws DatabaseException {
		try {
			getCurrentSession().delete(objeto);
			
			return objeto;
		} catch (HibernateException e) {
			throw new DatabaseException(e);
		}
	}

	/**
	 * @see CrudDAO#actualizar(Object)
	 */
	@Override
	@Transactional(propagation = Propagation.MANDATORY)
	public T actualizar(T objeto) throws DatabaseException {
		try {
			getCurrentSession().merge(objeto);
			getCurrentSession().flush();
			
			return objeto;
		} catch (HibernateException e) {
			throw new DatabaseException(e);
		}
	}

	/**
	 * @see CrudDAO#obtenerPorId(Object)
	 */
	@SuppressWarnings("unchecked")
	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public T obtenerPorId(ID id) throws RegistryNotFoundException {
		T entidadBuscada = (T) getCurrentSession().get(type, id);
		
		if (entidadBuscada == null) {
			String msjExcepcion = "No se encontró el registro buscado";
			throw new RegistryNotFoundException(msjExcepcion);
		}
		
		return entidadBuscada;
	}

	/**
	 * @see CrudDAO#obtenerTodos()
	 */
	@SuppressWarnings("unchecked")
	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public List<T> obtenerTodos() {
		return getCurrentSession().createCriteria(type).list();
	}
	
	/**
	 * Obtiene la sesión de Hibernate.
	 * @return Objeto que referencia la sesión actual.
	 */
	public Session getCurrentSession() {
		return sessionFactory.getCurrentSession();
	}

}
